/* @flow */

/**
 * Cross-platform code generation for component v-model
 * 该方法用于处理Vue组件上的v-model指令，在el对象上生成model属性，
 * 该对象指向一个对象，该属性的详细信息可查看compiler.js。
 */
export function genComponentModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  const { number, trim } = modifiers || {}

  //这里的$$v只是一个方法的参数的命名而已，可查看下方对于el.model.callback的注释
  const baseValueExpression = '$$v'
  let valueExpression = baseValueExpression
  //这里的trim和number其实都是在拼接el.model.callback回调函数的函数体部分
  if (trim) {
    valueExpression =
      `(typeof ${baseValueExpression} === 'string'` +
        `? ${baseValueExpression}.trim()` +
        `: ${baseValueExpression})`
  }
  if (number) {
    valueExpression = `_n(${valueExpression})`
  }
  const assignment = genAssignmentCode(value, valueExpression)

  el.model = {
    value: `(${value})`,
    expression: `"${value}"`,
    //比如value为'hello'，则callback的结果为：
    //function ($$v) {hello=$$v}，可见，callback代表v-model对应的事件的回调函数，
    //而$$v代表回传的参数值
    //另外，callback虽然保存的值为字符串，但是在codegen/index.js中的genData方法中处理el.model时，
    //会将返回的字符串拼接到一起
    callback: `function (${baseValueExpression}) {${assignment}}`
  }
}

/**
 * Cross-platform codegen helper for generating v-model value assignment code.
 */
export function genAssignmentCode (
  value: string, //属性值必须为一个合法的左值表达式，否则会报错
  assignment: string
): string {
  //比如value值为aa.bb[cc[dd]]，则modelRs的exp属性存储着aa.bb，而idx中存储着cc[dd]
  const modelRs = parseModel(value)
  if (modelRs.idx === null) {
    return `${value}=${assignment}`
  } else {
    return `$set(${modelRs.exp}, ${modelRs.idx}, ${assignment})`
  }
}

/**
 * parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
 *
 * for loop possible cases:
 *
 * - test 结果为：{exp:'test', idx:null}
 * - test[idx] 结果为：{exp:'test', idx:'idx'}
 * - test[test1[idx]] 结果为：{exp:'test', idx:'test1[idx]'}
 * - test["a"][idx] 结果为：{exp:'test["a"]', idx:'idx'}
 * - xxx.test[a[a].test1[idx]] 结果为：{exp:'xxx.test', idx:'a[a].test1[idx]'}
 * - test.xxx.a["asa"][test1[idx]] 结果为：{exp:'test.xxx.a["asa"]', idx:'test1[idx]'}
 *
 */
//expressionPos和expressionEndPos分别指向方括号表达式的开始与结束的下标，比如：'hello["world"]'，则expressionPos指向'['的下标位置，
//而expressionEndPos指向']'的下标位置
let len, str, chr, index, expressionPos, expressionEndPos

export function parseModel (val: string): Object {
  str = val
  len = str.length
  index = expressionPos = expressionEndPos = 0

  //val.indexOf('[') < 0为true时，说明在val中未找到'['字符
  //val.lastIndexOf(']') < len - 1为true时，首先说明val.indexOf('[') < 0为false，
  //说明存在'['符号，其次val.lastIndexOf(']') < len - 1为true，说明']'不是字符串的最后一个字符，
  //也就是类似于这种形式：'h[1]o'，此时，将其作为普通字符串使用。
  //对于val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1为false的情况是，存在'['符号，并且
  //']'符号为整个字符串的最后一个字符，类似于如下形式：'h[2]'
  if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
    return {
      exp: val,
      idx: null
    }
  }

  while (!eof()) {
    chr = next()
    /* istanbul ignore if */
    if (isStringStart(chr)) {
      parseString(chr)
    } else if (chr === 0x5B) {//0x5B：[
      parseBracket(chr)
    }
  }

  return {
    exp: val.substring(0, expressionPos),
    idx: val.substring(expressionPos + 1, expressionEndPos)
  }
}

function next (): number {
  return str.charCodeAt(++index)
}

function eof (): boolean {
  return index >= len
}

function isStringStart (chr: number): boolean {
  //0x22：双引号"
  //0x27：单引号'
  return chr === 0x22 || chr === 0x27
}

function parseBracket (chr: number): void {
  let inBracket = 1
  expressionPos = index
  while (!eof()) {
    chr = next()
    if (isStringStart(chr)) {
      parseString(chr)
      continue
    }
    if (chr === 0x5B) inBracket++ //0x5B：[
    if (chr === 0x5D) inBracket-- //0x5D：]
    if (inBracket === 0) {
      expressionEndPos = index
      break
    }
  }
}

//如果待解析的表达式中存在字符串，比如"hello['world']"，则在处理时直接跳过'world'这些字符，
//而该方法就是用于跳过这种形式的字符。
function parseString (chr: number): void {
  const stringQuote = chr
  while (!eof()) {
    chr = next()
    if (chr === stringQuote) {
      break
    }
  }
}
